NestJS 提供了一些常見、常用的 Transporter,像是:Redis、NATS、gRPC 等,但有時候開發者可能無法使用這些 Transporter 來達到目的,有可能是內建的功能不夠齊全,亦或是根本沒有提供相關的 Transporter,像是:Google Cloud Pub/Sub、Amazon Kinesis、NSQ 等,於是 NestJS 開放了 自訂傳輸器(Custom Transporter) 的功能,讓開發者可以量身打造自己的 Transporter,不論是微服務應用程式、Client 都可以自行實作,且這些 Custom Transporter 也會跟大多數內建 Transporter 一樣,使用上會採用相同的開發風格,是相當強大且富有彈性的設計!
注意:本篇會著重在 Custom Transporter 的介紹,並不會有真的實作內容。
要建立一個用於微服務應用程式的 Custom Transporter 需要實作一套 策略(Strategy),讓 NestJS 根據該 Strategy 執行啟動、關閉的相關方法。下方是範例程式碼,建立了一個名為 MyTransporter 的 class 並實作 CustomTransportStrategy:
import { CustomTransportStrategy } from '@nestjs/microservices';
export class MyTransporterServer implements CustomTransportStrategy {
listen(callback: () => void) {}
close() {}
}
從上方可以看出,CustomTransportStrategy 必須實作 listen 與 close 方法,listen 會在 app.listen 被呼叫時觸發,而 close 會在應用程式關閉時觸發。
補充:一般來說會用
Server作為 Strategy 的後綴,因為它會負責處理微服務應用程式的請求與事件。
通常在實作 Strategy 的時候,會去繼承 NestJS 提供的 Server,它提供了一些好用的屬性與方法讓開發者可以在Strategy 中使用。下方是繼承 Server 後的範例程式碼:
import { CustomTransportStrategy, Server } from '@nestjs/microservices';
export class MyTransporterServer extends Server implements CustomTransportStrategy {
listen(callback: () => void) {}
close() {}
}
繼承 Server 之後,有以下幾個重點屬性可以取用:
messageHandlers:用來記錄那些在 Controller 使用 @MessagePattern 與 @EventPattern 的 Handler,並以 Map 的方式保存,key 為 Pattern、value 為 Handler。serializer:用來將訊息序列化的 Helper,需實作 ConsumerSerializer 介面,透過其 serialize 方法將訊息轉換成要傳輸出去的格式。deserializer:用來將訊息反序列化的 Helper,需實作 ConsumerDeserializer 介面,透過其 deserialize 方法將訊息轉換成內部所需的格式。除了屬性之外,還有這些重點方法:
initializeSerializer:初始化 serializer,如果從參數中找不到 serializer 就會直接採用 IdentitySerializer,而 IdentitySerializer 不會執行任何的資料轉換。initializeDeserializer:初始化 deserializer,如果從參數中找不到 deserializer 就會直接採用 IncomingRequestDeserializer。IncomingRequestDeserializer 會判斷傳入的訊息是否符合 NestJS 內部定義的訊息格式,如果符合就會直接回傳訊息不做任何資料轉換;反之,會將訊息轉換成符合內部定義的格式。send:用於處理 Request-response 的情境。handleEvent:用於處理 Event-based 的情境。transformToObservable:將 Promise 或值轉換成 Observable。getOptionsProp:取得物件中特定 key 的值,可以設定找不到值所使用的預設值。補充:在
initializeDeserializer提及的內部定義格式為含有pattern或data的物件,pattern即 Pattern 的內容,data即主要的資料。
建立好的 Strategy 要如何使用呢?與內建 Transporter 的使用方式不同,會需要在 createMicroservice 的 options 帶入 strategy 選項,值為 Strategy 實例。下方是範例程式碼,在 main.ts 使用 MyTransporterServer:
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
import { MyTransporterServer } from './my.transporter';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
strategy: new MyTransporterServer(),
},
);
await app.listen();
}
bootstrap();
要跟使用 Custom Transporter 的微服務應用程式溝通的話,可以使用相關技術的 SDK,或是經過 Client Proxy 進行封裝,如果想要使用 Client Proxy,可以自訂一個 class,並繼承 ClientProxy。下方是範例程式碼:
import { ClientProxy, ReadPacket, WritePacket } from '@nestjs/microservices';
class MyClient extends ClientProxy {
async connect() {
// ...
}
async close() {
// ...
}
async dispatchEvent<T>(packet: ReadPacket): T {
// ...
}
publish(
packet: ReadPacket,
callback: (packet: WritePacket) => void
): VoidFunction {
// ...
}
}
一旦繼承了 ClientProxy,就必須實作 connect、close、dispatchEvent 以及 publish,這四個方法皆為 abstract,功能如下:
connect:建立連線時會觸發此方法,通常會將 Connection 資訊回傳並將該資訊保存起來,確保可以重用 Connection。close:關閉連線時會觸發此方法。dispatchEvent:用於處理 Event-based 的情境,如果沒有支援 Event-based,可以實作空的方法或是拋出相關錯誤。publish:用於處理 Request-response 的情境,如果沒有支援 Request-response,可以實作空的方法或是拋出相關錯誤。另外,它的回傳值是 VoidFunction,會在該次發送被取消時觸發。除了上述四個方法外,還有一些重要的屬性可以使用:
serializer:用來將訊息序列化的 Helper,需實作 ProducerSerializer 介面,透過其 serialize 方法將訊息轉換成要傳輸出去的格式。deserializer:用來將訊息反序列化的 Helper,需實作 ProducerDeserializer 介面,透過其 deserialize 方法將訊息轉換成內部所需的格式。還有以下這些方法可以使用:
initializeSerializer:初始化 serializer,如果從參數中找不到 serializer 就會直接採用 IdentitySerializer。initializeDeserializer:初始化 deserializer,如果從參數中找不到 deserializer 就會直接採用 IncomingResponseDeserializer。IncomingRequestDeserializer 會判斷傳入的訊息是否符合 NestJS 內部定義的訊息格式,如果符合就會直接回傳訊息不做任何資料轉換;反之,會將訊息轉換成符合內部定義的格式。getOptionsProp:取得物件中特定 key 的值,可以設定找不到值所使用的預設值。send:會結合 publish 來實現 Request-response 的方法,也是我們在使用 ClientProxy 經常會使用到的方法。emit:會結合 dispatchEvent 來實現 Event-based 的方法,也是我們在使用 ClientProxy 經常會使用到的方法。serializeResponse:在 send 方法收到 response 並序列化後,會將值帶入此方法,如果要針對 序列化後的值 做調整,可以覆寫此方法。serializeError:在 send 方法產生錯誤後,會將錯誤帶入此方法,如果要針對錯誤進行處理,可以覆寫此方法。今天的內容主要是解說 Custom Transporter 在實作上會使用到哪些東西,像是:CustomTransportStrategy、Server 與 ClientProxy,並針對 Server 與 ClientProxy 提供的一些重要屬性與方法做解說,但光是解說它們的用途還是沒有辦法很清楚知道該如何做出一個屬於自己的 Custom Transporter,所以下一篇會實際做一遍 NSQ Transporter,敬請期待。